# server/mcp_server.py
# 统一的MCP HTTP服务器，暴露所有工具在端口7888

import sys
import io
import os
import threading
import time
import re
import hashlib
import httpx
from pathlib import Path
from urllib.parse import urlparse, unquote
from mcp.server.fastmcp import FastMCP
from datetime import datetime
import execjs
import json
import pyaudio
import wave
import pygame

# 解决中文乱码
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

# 全局音频控制变量
audio_stream = None
audio_thread = None
is_playing = False
stop_playing = False
current_file = None

# 初始化pygame mixer
try:
    pygame.mixer.pre_init(frequency=22050, size=-16, channels=2, buffer=512)
    pygame.mixer.init()
    print("✅ pygame mixer 初始化成功")
except Exception as e:
    print(f"⚠️ pygame mixer 初始化失败: {e}")


def create_mcp_server(port: int = 7888) -> FastMCP:
    """创建并配置MCP服务器"""
    mcp = FastMCP(name="unified_mcp_server", port=port)
    
    @mcp.tool()
    async def search_netease_music(keyword: str) -> str:
        """
        网易云音乐搜索工具: 根据关键词搜索歌曲。
        """
        headers={
            'referer':'https://music.163.com/',
            'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
        }

        cookies = {
            'MUSIC_U': '007569B0C991DB20F0F1EB989C2DCFF7F181396D13790007192BC8B619E8470A08141899BBF5C32F9AAD43F6E7D76AFBFD36520DE938BA6F072327494C8D1A0B9DE1C4F5CC5CB47055ABDB5782AED865E0D620CA5E313D1437E646E6C213E3C85DAF80022526A235306EC7A67DF94FDDC19A2BE92DB0B91C3F3D1917029259D3029DEFBA6EBF2348189123D17D1BCF460C297720562AC697D50C0C51C3696BAC8D795679DEE74BA9F166E0CF6F333AF6CF535B931B810A8F1F0C88A1D5CD01790E6D2EEF7500E854290DF49FC47F662D6ACF717F24F92E21818D55878D0F809B440C2C0E1CDF55408B37E5FDBB1D48D1FB0FA53D23C3A9243165013441A093D23CE3BAF4649D24FB02CB6D037EF938F8A1EAC55CE82438DDC87E905FA860B964357F214AA65A451715F3541AA1451C5DC6601D09163FC9CE0AF4B1223A8176458E58BBDE4F17F71CF893511FC3E2BA206B1A7B983C3972446ED4E551B161332B89A44D723C5BD05A0C4AA8805657C0788EFB781A1E49C4C3E0133A4EEA1629441EFBD8852B1DBB7A4E1669370CFFA2749D9A0B80E4ECDC6CD526CEEB5A51B1946B',
        }

        params = {
            'csrf_token': '1389aa3d1643ab3815a4a79534d9242e',
        }

        url='https://music.163.com/weapi/cloudsearch/get/web'

        try:
            with open('server/wyy.js', encoding='utf-8') as f:
                js_code = execjs.compile(f.read())
                i1x = {
                    "hlpretag": "<span class=\"s-fc7\">",
                    "hlposttag": "</span>",
                    "s": keyword,
                    "type": "1",
                    "offset": "0",
                    "total": "true",
                    "limit": "5",
                    "csrf_token": "deebd90ed14db91213cf4f7ef9e376b7"
                }
                r = js_code.call('GetSign',i1x)

                data = {
                    "params":r.get("encText"),
                    "encSecKey":r.get("encSecKey")
                }

                # 使用httpx异步请求
                async with httpx.AsyncClient(timeout=10.0) as client:
                    response = await client.post(
                        url=url,
                        params=params,
                        cookies=cookies,
                        headers=headers,
                        data=data,
                    )
                    response.raise_for_status()

                result = json.loads(response.text)
                if result.get("code") == 200 and "songs" in result["result"]:
                    songs_info = []
                    for song in result["result"]["songs"]:
                        # 检查是否为VIP歌曲：fee=1代表需要VIP/付费，fee=0代表免费
                        # 同时结合播放权限pl判断（0=无权限，非0=有免费播放权限）
                        privilege = song.get("privilege", {})
                        is_vip = (privilege.get("fee") == 1 and privilege.get("pl") == 0)
                        
                        # 只输出非VIP歌曲
                        if not is_vip:
                            # 处理多歌手情况（用逗号拼接）
                            singers = ",".join([ar["name"] for ar in song["ar"]])
                            # 时长转换为 分:秒 格式（原单位是毫秒）
                            duration = song["dt"] // 1000  # 转换为秒
                            
                            song_info = f"""
-{'-' * 50}
歌曲名称: {song['name']}
歌曲ID: {song['id']}
歌曲专辑: {song['al']['name']}
歌曲歌手: {singers}
歌曲时长: {duration // 60}:{duration % 60:02d}
歌曲封面: {song['al']['picUrl']}
是否VIP歌曲: 否
-{'-' * 50}
                            """
                            songs_info.append(song_info)
                    
                    if songs_info:
                        return "\n".join(songs_info)
                    else:
                        return "未找到非VIP歌曲，请尝试其他关键词"
                else:
                    return f"搜索失败：{result.get('message', '未知错误')}"
                    
        except FileNotFoundError:
            return "错误：找不到wyy.js文件"
        except httpx.HTTPStatusError as e:
            return f"请求失败：HTTP 状态码错误 {e.response.status_code}"
        except httpx.RequestError:
            return "请求失败：网络连接错误或超时"
        except Exception as e:
            return f"发生未知错误：{type(e).__name__}: {str(e)}"


    @mcp.tool()
    async def download_files(url: str ,filename: str) -> str:
        """
        下载文件工具: 从URL下载文件到指定目录
        
        Args:
            url: 要下载的文件URL链接
        """
        # 获取下载目录，优先使用环境变量，否则使用默认目录
        download_dir = os.getenv('DOWNLOAD_DIR','D:/1')
        download_path = Path(download_dir)
        
        # 确保下载目录存在
        download_path.mkdir(parents=True, exist_ok=True)
        
        try:            
            # 如果没有传入文件名，生成一个默认名称
            if not filename or '.' not in filename:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"download_{timestamp}.bin"
            
            # 完整的文件路径
            file_path = download_path / filename
            
            # 如果文件已存在，添加序号
            counter = 1
            original_path = file_path
            while file_path.exists():
                name, ext = os.path.splitext(original_path.name)
                file_path = download_path / f"{name}_{counter}{ext}"
                counter += 1
            
            # 下载文件
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
            }
            
            async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
                async with client.stream('GET', url, headers=headers) as response:
                    response.raise_for_status()
                    
                    # 获取文件大小（如果可用）
                    total_size = response.headers.get('content-length')
                    if total_size:
                        total_size = int(total_size)
                    
                    # 写入文件
                    downloaded_size = 0
                    with open(file_path, 'wb') as f:
                        async for chunk in response.aiter_bytes(chunk_size=8192):
                            f.write(chunk)
                            downloaded_size += len(chunk)
                            
                            # 显示下载进度（每1MB显示一次）
                            if downloaded_size % (1024 * 1024) == 0:
                                progress = f"已下载: {downloaded_size / 1024 / 1024:.1f}MB"
                                if total_size:
                                    progress += f" / {total_size / 1024 / 1024:.1f}MB ({downloaded_size / total_size * 100:.1f}%)"
                                print(f"下载进度: {progress}")
            
            # 获取文件大小
            file_size = file_path.stat().st_size
            file_size_mb = file_size / 1024 / 1024
            
            return (
                f"✅ 下载完成！\n"
                f"📁 保存位置: {file_path.absolute()}\n"
                f"📄 文件名: {file_path.name}\n"
                f"📊 文件大小: {file_size_mb:.2f} MB\n"
                f"🔗 原始链接: {url}"
            )
            
        except httpx.HTTPStatusError as e:
            return f"❌ 下载失败：HTTP错误 {e.response.status_code} - {e.response.reason_phrase}"
        except httpx.RequestError as e:
            return f"❌ 下载失败：网络请求错误 - {str(e)}"
        except Exception as e:
            return f"❌ 下载失败：未知错误 - {str(e)}"

    @mcp.tool()
    async def get_nowtime() -> str:
        """
        获取当前时间,格式为: 2025-10-15 10:00:00
        """
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    @mcp.tool()
    async def parse_netease_music(music_url: str) -> str:
        """
        网易云音乐解析接口: 根据链接返回标题与直链。
        
        Args:
            music_url: 网易云音乐的分享链接 (例如: https://music.163.com/#/song?id=123456)
        """
        # 提取歌曲 ID
        id_match = re.search(r'(?:/song|\#/song)\?id=(\d+)', music_url)
        if not id_match:
            return "解析失败：无效的网易云音乐链接，未找到歌曲 ID。"

        song_id = id_match.group(1)
        # 构建直链
        download_url = f'https://music.163.com/song/media/outer/url?id={song_id}'

        # 请求头
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'Referer': 'https://music.163.com',
        }

        try:
            # 请求页面获取标题
            async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
                response = await client.get(music_url, headers=headers)
                response.raise_for_status()

            # 解析歌曲标题
            title_match = re.search(r'<title>(.*?)</title>', response.text, re.DOTALL)
            
            if title_match:
                # 清理标题，移除 "- 网易云音乐" 等后缀
                title = title_match.group(1).split('-')[0].strip()
            else:
                title = f'歌曲ID_{song_id}'
            
            # 尝试获取真实的下载链接（处理重定向）
            try:
                async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
                    # 发送HEAD请求获取最终的重定向URL
                    head_response = await client.head(download_url, headers=headers)
                    final_url = str(head_response.url)
                    
                    # 如果重定向到了不同的域名，说明是真实的下载链接
                    if 'music.163.com' not in final_url:
                        download_url = final_url
            except:
                # 如果获取重定向失败，使用原始链接
                pass
            
            return f"标题: {title}\n直链: {download_url}\n\n💡 提示：可以使用 download_files 工具直接下载此文件"

        except httpx.HTTPStatusError as e:
            return f"请求失败：HTTP 状态码错误 {e.response.status_code}。"
        except httpx.RequestError:
            return "请求失败：网络连接错误或超时。"
        except Exception as e:
            return f"发生未知错误：{type(e).__name__}"

    @mcp.tool()
    async def parse_video_url(video_url: str) -> str:
        """
        解析抖音或B站的视频链接，返回标题和媒体链接
        
        Args:
            video_url: 抖音或B站的分享链接
        """
        if not re.search(r'(douyin\.com|bilibili\.com)', video_url):
            return "解析失败：请输入一个有效的抖音或B站视频链接。"

        api_url = 'https://api.snapany.com/v1/extract'
        
        # 动态生成 snapany.com 所需的加密请求头
        locale = 'zh'
        qc = '6HTugjCXxR'
        timestamp = str(int(time.time() * 1000))
        combined_string = f"{video_url}{locale}{timestamp}{qc}"
        g_footer = hashlib.md5(combined_string.encode('utf-8')).hexdigest()

        headers = {
            'accept': '*/*',
            'accept-language': 'zh',
            'content-type': 'application/json',
            'origin': 'https://snapany.com',
            'priority': 'u=1, i',
            'referer': 'https://snapany.com/',
            'sec-ch-ua': '"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-site',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
            'g-footer': g_footer,
            'g-timestamp': timestamp,
        }
        payload = {'link': video_url}

        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.post(
                    api_url,
                    headers=headers,
                    json=payload
                )
                response.raise_for_status()
            data = response.json()

            # 解析数据
            title = data.get('text', '无标题')
            medias = data.get('medias', [])

            if not medias:
                return f"解析成功，但未能找到媒体文件。\n标题: {title}"

            # 提取信息
            video_info = medias[0]
            result_video_url = video_info.get('resource_url', '未找到')
            preview_url = video_info.get('preview_url', '未找到')
            # 最后一个通常是音频
            audio_url = medias[-1].get('resource_url') if len(medias) > 1 else '无音频'

            return (
                f"标题: {title}\n"
                f"视频直链: {result_video_url}\n"
                f"音频直链: {audio_url}\n"
                f"视频封面: {preview_url}"
            )
        except httpx.HTTPStatusError as e:
            return f"解析失败：API 返回错误状态码 {e.response.status_code}。"
        except httpx.RequestError:
            return "解析失败：请求 API 时网络出错或超时。"
        except Exception:
            return "解析失败：服务器发生未知错误。"

    @mcp.tool()
    async def play_music(file_path: str) -> str:
        """
        播放音乐文件工具: 使用pygame播放本地音频文件
        
        Args:
            file_path: 音频文件的完整路径 (支持wav, mp3等格式)
        """
        global audio_stream, audio_thread, is_playing, stop_playing, current_file
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return f"❌ 文件不存在: {file_path}"
        
        # 如果正在播放，先停止
        if is_playing:
            await stop_music()
            time.sleep(0.5)  # 等待停止完成
        
        try:
            # 重置停止标志
            stop_playing = False
            is_playing = True
            current_file = file_path
            
            # 使用pygame播放所有格式
            def play_audio():
                global is_playing, stop_playing
                try:
                    print(f"🎵 开始播放文件: {file_path}")
                    
                    # 加载并播放音频文件
                    pygame.mixer.music.load(file_path)
                    pygame.mixer.music.play()
                    
                    print(f"🎵 音频已开始播放，pygame状态: {pygame.mixer.music.get_busy()}")
                    
                    # 等待播放完成或被停止
                    while pygame.mixer.music.get_busy() and not stop_playing:
                        time.sleep(0.1)
                    
                    # 如果被手动停止
                    if stop_playing:
                        pygame.mixer.music.stop()
                        print("⏹️ 音频播放被手动停止")
                    else:
                        print("✅ 音频播放完成")
                    
                    # 播放结束后重置状态
                    is_playing = False
                    stop_playing = False
                    
                except Exception as e:
                    print(f"❌ 播放音频文件时出错: {e}")
                    is_playing = False
                    stop_playing = False
            
            # 启动播放线程
            audio_thread = threading.Thread(target=play_audio, daemon=True)
            audio_thread.start()
            
            # 等待一小段时间确保播放开始
            time.sleep(0.2)
            
            return f"🎵 开始播放: {os.path.basename(file_path)}\n📁 文件路径: {file_path}\n🎮 播放器状态: {'忙碌' if pygame.mixer.music.get_busy() else '空闲'}"
            
        except Exception as e:
            is_playing = False
            return f"❌ 播放失败: {str(e)}"

    @mcp.tool()
    async def stop_music() -> str:
        """
        停止音乐播放工具: 停止当前正在播放的音频
        """
        global audio_stream, audio_thread, is_playing, stop_playing, current_file
        
        if not is_playing:
            return "ℹ️ 当前没有正在播放的音乐"
        
        try:
            print("⏹️ 正在停止音乐播放...")
            
            # 设置停止标志
            stop_playing = True
            
            # 停止pygame播放
            pygame.mixer.music.stop()
            
            # 等待线程结束
            if audio_thread and audio_thread.is_alive():
                audio_thread.join(timeout=2.0)
            
            # 重置状态
            is_playing = False
            stop_playing = False
            current_file = None
            
            print("✅ 音乐播放已停止")
            return "⏹️ 音乐播放已停止"
            
        except Exception as e:
            print(f"❌ 停止播放时出错: {e}")
            return f"❌ 停止播放时出错: {str(e)}"

    @mcp.tool()
    async def get_audio_status() -> str:
        """
        获取音频播放状态工具: 查看当前音频播放状态
        """
        global is_playing, stop_playing, current_file
        
        status = "🎵 音频播放状态:\n"
        status += f"   正在播放: {'是' if is_playing else '否'}\n"
        status += f"   停止标志: {'是' if stop_playing else '否'}\n"
        status += f"   当前文件: {current_file if current_file else '无'}\n"
        
        pygame_busy = pygame.mixer.music.get_busy()
        status += f"   pygame播放器: {'忙碌中' if pygame_busy else '空闲'}\n"
        
        # 添加更详细的状态信息
        if is_playing and not pygame_busy:
            status += "   ⚠️ 状态异常: 标记为播放中但pygame显示空闲\n"
        elif not is_playing and pygame_busy:
            status += "   ⚠️ 状态异常: 标记为未播放但pygame显示忙碌\n"
        elif is_playing and pygame_busy:
            status += "   ✅ 状态正常: 正在播放中\n"
        else:
            status += "   ✅ 状态正常: 未播放\n"
        
        return status

    @mcp.tool()
    async def test_audio_system() -> str:
        """
        测试音频系统工具: 检查音频系统是否正常工作
        """
        try:
            # 检查pygame mixer状态
            mixer_info = {
                "pygame版本": pygame.version.ver,
                "mixer初始化": pygame.mixer.get_init(),
                "当前播放": pygame.mixer.music.get_busy(),
                "音量": pygame.mixer.music.get_volume()
            }
            
            result = "🔧 音频系统测试结果:\n"
            for key, value in mixer_info.items():
                result += f"   {key}: {value}\n"
            
            # 尝试播放测试音频文件
            try:
                # 使用相对路径的测试音频文件
                test_audio_file = "server/Hello, I am Javis..mp3"
                
                # 检查文件是否存在
                if not os.path.exists(test_audio_file):
                    result += f"   ⚠️ 测试音频文件不存在: {test_audio_file}\n"
                else:
                    # 尝试播放测试音频
                    pygame.mixer.music.load(test_audio_file)
                    pygame.mixer.music.play()
                    
                    # 等待播放完成
                    while pygame.mixer.music.get_busy():
                        time.sleep(0.1)
                    
                    result += "   ✅ 测试音频播放: 成功\n"
                
            except Exception as e:
                result += f"   ⚠️ 测试音频播放: 失败 ({str(e)})\n"
            
            return result
            
        except Exception as e:
            return f"❌ 音频系统测试失败: {str(e)}"
    
    return mcp


def run_mcp_server(port: int = 7888):
    """运行MCP服务器"""
    print("🚀 启动MCP HTTP服务器")
    print(f"🌐 服务地址: http://127.0.0.1:{port}")
    print("📌 按 CTRL+C 停止服务")
    
    mcp = create_mcp_server(port)
    
    try:
        mcp.run(transport='streamable-http')
    except KeyboardInterrupt:
        print("\n🛑 服务器已停止")
    except Exception as e:
        print(f"\n❌ 服务器启动失败: {e}")


def start_server_in_background(port: int = 7888):
    """在后台启动MCP服务器"""
    def run_server():
        run_mcp_server(port)
    
    server_thread = threading.Thread(target=run_server, daemon=True)
    server_thread.start()
    time.sleep(2)  # 等待服务器启动
    return server_thread
